package main

import (
	"bytes"

	"errors"
	"fmt"
	"io"
	"log"
	"strconv"
)

const (
	FrameInfoSize = 128
	SOI           = 0x7E
	EOI           = 0x0D
)

type CommandID int

const (
	CmdFirst CommandID = iota
	GetAnalogDataF
	GetAnalogDataD
	GetOnOffData
	GetWarningData
	RemoteCommand
	GetSysParamF
	GetSysParamD
	SetSysParamF
	SetSysParamD
	GetSysHistF
	GetSysHistD
	GetWarnHist
	GetTime
	SetTime
	GetProtoVer
	GetDevAddr
	GetVendorInfo
	CmdLast
)

var commands = map[CommandID]string{
	GetAnalogDataF: "2A41",
	GetAnalogDataD: "2A42",
	GetOnOffData:   "2A43",
	GetWarningData: "2A44",
	RemoteCommand:  "2A45",
	GetSysParamF:   "2A46",
	GetSysParamD:   "2A47",
	SetSysParamF:   "2A48",
	SetSysParamD:   "2A49",
	GetSysHistF:    "2A4A",
	GetSysHistD:    "2A4B",
	GetWarnHist:    "2A4C",
	GetTime:        "2A4D",
	SetTime:        "2A4E",
	GetProtoVer:    "2A4F",
	GetDevAddr:     "2A50",
	GetVendorInfo:  "2A51",
}

var rtnVals = []string{
	"OK",
	"Bad VER",
	"Bad CHKSUM",
	"Bad LCHKSUM",
	"Invalid CID2",
	"Bad Command Format",
	"Bad Data",
}

type Frame struct {
	SOI    byte
	VER    [2]byte
	ADR    [2]byte
	CID    [4]byte
	LEN    [4]byte
	INFO   [FrameInfoSize]byte
	CHKSUM [4]byte
	EOI    byte
	InfoLen int
}

func (f *Frame) Clear() {
	*f = Frame{}
}

func (f *Frame) Init(cmd CommandID, ver, addr string, data []byte) error {
	if len(data) > FrameInfoSize {
		return fmt.Errorf("data length exceeds frame info size: %d > %d", len(data), FrameInfoSize)
	}

	f.SOI = SOI
	copy(f.VER[:], ver)
	copy(f.ADR[:], addr)
	copy(f.CID[:], commands[cmd])
	f.InfoLen = len(data)
	copy(f.INFO[:], data)
	f.EOI = EOI

	f.SetLEN()
	f.SetCHKSUM()

	return nil
}

func (f *Frame) SetLEN() {
	dlen := uint16(f.InfoLen)
	lenchk := (dlen & 0x000F) + ((dlen & 0x00F0) >> 4) + ((dlen & 0x0F00) >> 8) + ((dlen & 0xF000) >> 12)
	lenchk = ^lenchk + 1
	lelen := htole16(dlen)
	lenWithChk := uint16(lelen) | uint16(lenchk<<12)
	copy(f.LEN[:], fmt.Sprintf("%04X", lenWithChk))
}

func (f *Frame) SetCHKSUM() {
	buf := bytes.Join([][]byte{f.VER[:], f.ADR[:], f.CID[:], f.LEN[:], f.INFO[:f.InfoLen]}, nil)
	sum := uint32(0)
	for _, b := range buf {
		sum += uint32(b)
	}
	sum %= 65536
	chksum := ^uint16(sum) + 1
	copy(f.CHKSUM[:], fmt.Sprintf("%04X", chksum))
}

func (f *Frame) Send(w io.Writer) error {
	if _, err := w.Write([]byte{f.SOI}); err != nil {
		return err
	}
	if _, err := w.Write(f.VER[:]); err != nil {
		return err
	}
	if _, err := w.Write(f.ADR[:]); err != nil {
		return err
	}
	if _, err := w.Write(f.CID[:]); err != nil {
		return err
	}
	if _, err := w.Write(f.LEN[:]); err != nil {
		return err
	}
	if _, err := w.Write(f.INFO[:f.InfoLen]); err != nil {
		return err
	}
	if _, err := w.Write(f.CHKSUM[:]); err != nil {
		return err
	}
	if _, err := w.Write([]byte{f.EOI}); err != nil {
		return err
	}
	return nil
}

func (f *Frame) Read(r io.Reader) error {
	f.Clear()
	buf := make([]byte, 1+2+2+4+4+FrameInfoSize+4+1)
	if _, err := io.ReadFull(r, buf); err != nil {
		return err
	}

	f.SOI = buf[0]
	copy(f.VER[:], buf[1:3])
	copy(f.ADR[:], buf[3:5])
	copy(f.CID[:], buf[5:9])
	copy(f.LEN[:], buf[9:13])
	copy(f.INFO[:], buf[13:13+FrameInfoSize])
	copy(f.CHKSUM[:], buf[13+FrameInfoSize:13+FrameInfoSize+4])
	f.EOI = buf[13+FrameInfoSize+4]

	if f.SOI != SOI {
		return errors.New("bad SOI")
	}

	rtn := valFromHex(f.CID[2:4])
	if rtn != 0 {
		return fmt.Errorf("bad RTN: %s", rtnVals[rtn])
	}

	lenWithChk := string(f.LEN[:])
	dlen, err := strconv.ParseUint(lenWithChk[1:5], 16, 16)
	if err != nil {
		return fmt.Errorf("bad LEN: %v", err)
	}
	f.InfoLen = int(dlen)

	if f.InfoLen > FrameInfoSize {
		return fmt.Errorf("bad LEN: %d", f.InfoLen)
	}

	expectedChksum := f.calculateCHKSUM()
	if !bytes.Equal(expectedChksum[:], f.CHKSUM[:]) {
		return fmt.Errorf("bad CHKSUM: expected %s, got %s", expectedChksum, f.CHKSUM)
	}

	return nil
}

func (f *Frame) calculateCHKSUM() [4]byte {
	buf := bytes.Join([][]byte{f.VER[:], f.ADR[:], f.CID[:], f.LEN[:], f.INFO[:f.InfoLen]}, nil)
	sum := uint32(0)
	for _, b := range buf {
		sum += uint32(b)
	}
	sum %= 65536
	chksum := ^uint16(sum) + 1
	var chksumBytes [4]byte
	copy(chksumBytes[:], fmt.Sprintf("%04X", chksum))
	return chksumBytes
}

func valFromHex(hexStr []byte) int {
	val, err := strconv.ParseInt(string(hexStr), 16, 64)
	if err != nil {
		log.Printf("error parsing hex value: %v", err)
		return 0
	}
	return int(val)
}

func htole16(x uint16) uint16 {
	return x<<8 | x>>8
}


func main() {
	frame := Frame{}
	data := []byte{0x01, 0x02, 0x03, 0x04}
	err := frame.Init(GetAnalogDataF, "21", "01", data)
	if err != nil {
		fmt.Println("Error initializing frame:", err)
		return
	}

	// Simulate sending the frame
	var buf bytes.Buffer
	if err := frame.Send(&buf); err != nil {
		fmt.Println("Error sending frame:", err)
		return
	}

	// Simulate receiving the frame
	receivedFrame := Frame{}
	if err := receivedFrame.Read(&buf); err != nil {
		fmt.Println("Error reading frame:", err)
		return
	}

	fmt.Println("Frame received successfully")
}